#pragma once
#include <type_traits>
#include <SFML/System/Vector2.hpp>
#include <SFML/System/Vector3.hpp>
#include <SFML/Window/VideoMode.hpp>
#include <SFML/Graphics/Color.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/Text.hpp>
#include <boost/describe.hpp>
#include <boost/mp11.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <nice2d/metaobject.h>

namespace sf
{

#define DESCRIBE_VECTOR2(v2)\
BOOST_DESCRIBE_STRUCT(v2, (), (x, y))

#define DESCRIBE_VECTOR3(v3)\
BOOST_DESCRIBE_STRUCT(v3, (), (x, y,z))

#define DESCRIBE_RECT(rect)\
BOOST_DESCRIBE_STRUCT(rect, (), (left, top, width, height))

DESCRIBE_VECTOR2(Vector2i)
DESCRIBE_VECTOR2(Vector2u)
DESCRIBE_VECTOR2(Vector2f)

DESCRIBE_VECTOR3(Vector3i)
DESCRIBE_VECTOR3(Vector3f)

DESCRIBE_RECT(IntRect)
DESCRIBE_RECT(FloatRect)

BOOST_DESCRIBE_STRUCT(VideoMode, (), (width, height, bitsPerPixel))
BOOST_DESCRIBE_STRUCT(Color, (), (r, g, b, a))

BOOST_DESCRIBE_ENUM(Text::Style, Regular, Bold, Italic, Underlined, StrikeThrough)

}

using PTree = boost::property_tree::ptree;

class Serializer
{
    DECLARE_ABSTRACT_OBJECT(Serializer)
public:
    virtual PTree serialize() = 0;
    PTree serializeThisClass()
    {
        PTree node;
        node.add_child(getObjectName(),serialize());
        return node;
    }

    virtual void deserialize(const PTree &node) = 0;

    void saveToXml(const std::string& file);
    void saveToXml(const std::string& file,const std::string& nodeName);

    template<class T>
    void serializeObjects(T begin,T end,PTree& node,const std::string& tag)
    {
        PTree list;
        std::for_each(begin,end,[&](auto item)
        {
            list.add_child(item->getObjectName(),item->serialize());
        });
        node.add_child(tag,list);
    }
};

using SerializerPointer = std::shared_ptr<Serializer>;

template<class T,
         class M = boost::describe::describe_members< T,
                                                     boost::describe::mod_public | boost::describe::mod_protected >>
static PTree serializeObject(const T &t)
{
    PTree node;
    boost::mp11::mp_for_each<M>([&](auto I) {
        node.add(I.name, t.*I.pointer);
    });
    return node;
}

template<class C>
static PTree serializeContainer(const C& container)
{
    PTree node;
    using ItemType = typename C::value_type;
    std::for_each(container.begin(),container.end(),[&](const auto& item)
    {
        PTree child;

        if constexpr(std::is_pointer_v<ItemType>)
            item->serialize(child);
        else
            child = serializeObject(item);

        node.add_child("item",child);
    });

    return node;
}

sf::Vector2f deserializeVector2f(const PTree &node);
sf::Vector2i deserializeVector2i(const PTree &node);
sf::Vector2u deserializeVector2u(const PTree &node);
sf::Color deserializeColor(const PTree &node);
sf::IntRect deserializeIntRect(const PTree &node);
sf::FloatRect deserializeFloatRect(const PTree &node);
sf::VideoMode deserializeViewMode(const PTree &node);

void deserializeObject(const PTree &node,sf::Vector2f& value);
void deserializeObject(const PTree &node,sf::Vector2i& value);
void deserializeObject(const PTree &node,sf::Vector2u& value);
void deserializeObject(const PTree &node,sf::Color& value);
void deserializeObject(const PTree &node,sf::IntRect& value);
void deserializeObject(const PTree &node,sf::FloatRect& value);
void deserializeObject(const PTree &node,sf::VideoMode& value);

#define SERIALIZE_BASE_OBJECT(node,object)\
node.add_child(#object,object::serialize());

#define SERIALIZE_SIMPLE_OBJECT(node,object)\
node.add_child(#object,serializeObject(object));

#define DESERIALIZE_BASE_OBJECT(node,object)\
object::deserialize(node.get_child(#object));

#define DESERIALIZE_SIMPLE_OBJECT(node,object)\
{\
    auto option = node.get_child_optional(#object);\
    if(option.has_value())\
        deserializeObject(option.value(),object);\
}

#define ASSIGN_DESERIALIZE_VALUE(node,var)\
{\
    auto option = node.get_optional<decltype(var)>(std::string(#var));\
    if(option.has_value())\
        var = option.value();\
}

#define GET_DESERIALIZE_VALUE_IN_DATA(node,var)\
{\
    auto option = node.get_optional<decltype(data->var)>(std::string(#var));\
    if(option.has_value())\
        data->var = option.value();\
}
